﻿using System;
using System.Collections.Generic;
using System.Globalization;
using Cpuss.ReportGenerator.Properties;

namespace Cpuss.ReportGenerator
{

    /// <summary>
    /// Parses the flags from the console.
    /// </summary>
    public class FlagParser
    {

        private readonly string[] _inputStream;
        private int _repeat;
        private int _small;
        private int _medium;
        private int _large;
        private string _strategy;
        private string _report;
        private string _outdir;
        private List<object> _constructorArguments;
        private int _window;

        /// <summary>
        /// Initializes a new instance of the <see cref="FlagParser"/> class.
        /// </summary>
        /// <param name="inputStream">The input stream.</param>
        /// <exception cref="ArgumentNullException"><strong>inputStream</strong> is <strong>null</strong>.</exception>
        public FlagParser(string[] inputStream)
        {
            if (inputStream == null)
            {
                throw new ArgumentNullException("inputStream");
            }

            _inputStream = inputStream;
            _constructorArguments = new List<object>();
        }

        /// <summary>
        /// Parses the input stream.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// <para>
        /// A <strong>flag</strong> has no associated <strong>value</strong>.
        /// </para>
        /// <para>
        /// -- or --
        /// </para>
        /// <para>
        /// A <strong>flag</strong> associated with an integer value could <strong>not</strong> be parsed.
        /// </para>
        /// <para>
        /// A <strong>flag</strong> is in an <strong>incorrect</strong> format.
        /// </para>
        /// <para>
        /// A <strong>flag</strong> is not <strong>recognised</strong>.
        /// </para>
        /// </exception>
        public void Parse()
        {
            for (int i = 0; i < _inputStream.Length; i++)
            {
                if (IsCorrectForm(_inputStream[i]))
                {
                    ProcessFlag(_inputStream[i]);
                }
                else
                {
                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The flag: {0} is in an incorrect format", _inputStream[i]));
                }
            }
        }

        /// <summary>
        /// Extracts the value of a flag for later use.
        /// </summary>
        /// <param name="rawFlag">The flag to process.</param>
        private void ProcessFlag(string rawFlag)
        {
            int colonPos = rawFlag.IndexOf(':');
            string flag = rawFlag.Substring(0, colonPos).ToUpperInvariant();
            // verify the flag has a value
            if (colonPos == rawFlag.Length - 1)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The flag: {0} has no associated value.", flag.ToLowerInvariant()));
            }
            switch (flag)
            {
                case "/REPEAT":
                    ParseInteger(rawFlag, colonPos, flag, ref _repeat);
                    break;
                case "/STRATEGY":
                    ParseStrategy(rawFlag, colonPos);
                    break;
                case "/REPORT":
                    _report = rawFlag.Substring(colonPos + 1);
                    break;
                case "/SMALL":
                    ParseInteger(rawFlag, colonPos, flag, ref _small);
                    break;
                case "/MEDIUM":
                    ParseInteger(rawFlag, colonPos, flag, ref _medium);
                    break;
                case "/LARGE":
                    ParseInteger(rawFlag, colonPos, flag, ref _large);
                    break;
                case "/OUTDIR":
                    _outdir = rawFlag.Substring(colonPos + 1);
                    break;
                case "/WINDOW":
                    ParseInteger(rawFlag, colonPos, flag, ref _window);
                    break;
                default:
                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The flag: {0} is not recognised.", flag.ToLowerInvariant()));
            }
        }

        /// <summary>
        /// Parses a strategy and its arguments if it has any.
        /// </summary>
        /// <param name="rawFlag">Raw flag.</param>
        /// <param name="colonPos">Colon position.</param>
        private void ParseStrategy(string rawFlag, int colonPos)
        {
            int comma = rawFlag.IndexOf(',');
            if (comma < 0)
            {
                _strategy = rawFlag.Substring(colonPos + 1);
            }
            else
            {
                _strategy = rawFlag.Substring(colonPos + 1, comma - (colonPos + 1));
                List<int> positions = IndexesOf(rawFlag, ',');
                for (int i = 0; i < positions.Count; i++)
                {
                    int arg = 0;
                    bool parsed;
                    // extract the arguments for the strategy
                    if (positions.Count == 1 || i + 1 == positions.Count)
                    {
                        parsed = int.TryParse(rawFlag.Substring(positions[i] + 1), out arg);
                    }
                    else
                    {
                        parsed = int.TryParse(rawFlag.Substring(positions[i] + 1, positions[i + 1] - (positions[i] + 1)), out arg);
                        
                    }
                    // verify the int32 parse
                    if (parsed)
                    {
                        _constructorArguments.Add(arg);
                    }
                    else
                    {
                        throw new ArgumentException(Resources.ParameterNotInt32);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the indexes of a character in a string.
        /// </summary>
        /// <param name="word">String to get indexes from.</param>
        /// <param name="value">Value to get indexes of.</param>
        /// <returns>Indexes of the value in the string.</returns>
        private static List<int> IndexesOf(string word, char value)
        {
            List<int> indexes = new List<int>();
            for (int i = 0; i < word.Length; i++)
            {
                if (word[i] == value)
                {
                    indexes.Add(i);
                }
            }
            return indexes;
        }

        /// <summary>
        /// Attempts to parse the integer value of a flag.
        /// </summary>
        /// <param name="rawFlag">Raw flag.</param>
        /// <param name="colonPos">Colon position.</param>
        /// <param name="flag">Flag name.</param>
        /// <param name="value">Value to copy parsed int to.</param>
        /// <exception cref="ArgumentException">Integer could not be parsed.</exception>
        private static void ParseInteger(string rawFlag, int colonPos, string flag, ref int value)
        {
            bool parsed = int.TryParse(rawFlag.Substring(colonPos + 1), out value);
            if (!parsed)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The value of flag: {0} should be an integer.", flag.ToLowerInvariant()));
            }
        }

        /// <summary>
        /// Determines whether the rawFlag is in the correct form.
        /// </summary>
        /// <param name="flag">Flag to check form of.</param>
        /// <returns>True if the rawFlag is in the correct basic form; false otherwise.</returns>
        private static bool IsCorrectForm(string flag)
        {
            return flag[0] != '/' || flag.IndexOf(':') < 0 ? false : true;
        }

        /// <summary>
        /// Gets the input stream to the parser.
        /// </summary>
        /// <returns>Input stream of flags.</returns>
        public string[] GetInputStream()
        {
            return _inputStream;
        }

        /// <summary>
        /// Gets the number of times to repeat a simulation.
        /// </summary>
        public int Repeat
        {
            get
            {
                return _repeat;
            }
        }

        /// <summary>
        /// Gets the name of the strategy.
        /// </summary>
        public string Strategy
        {
            get
            {
                return _strategy;
            }
        }

        /// <summary>
        /// Gets the name of the report file.
        /// </summary>
        public string Report
        {
            get
            {
                return _report;
            }
        }

        /// <summary>
        /// Gets the number of small processes.
        /// </summary>
        public int Small
        {
            get
            {
                return _small;
            }
        }

        /// <summary>
        /// Gets the number of medium processes.
        /// </summary>
        public int Medium
        {
            get
            {
                return _medium;
            }
        }

        /// <summary>
        /// Gets the number of large processes.
        /// </summary>
        public int Large
        {
            get
            {
                return _large;
            }
        }

        /// <summary>
        /// Gets the output directory.
        /// </summary>
        public string Outdir
        {
            get
            {
                return _outdir;
            }
        }

        /// <summary>
        /// Gets the arguments for the strategy constructor.
        /// </summary>
        public List<object> ConstructorArguments
        {
            get
            {
                return _constructorArguments;
            }
        }

        /// <summary>
        /// Gets the window timeframe being used.
        /// </summary>
        public int Window
        {
            get { return _window; }
        }
    }
}
